﻿using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Collections.Generic;
using Red.Toolkit.Profiler.Logic;

namespace Red.Toolkit.Profiler.Drawing
{
    public class GraphComponent : FrameworkElement
    {
        private enum MouseDragMode
        {
            None,
            Move
        }

        private TimelineStyle _timelineStyle;
        private TimelineParams _timelineParams;

        private MouseDragMode _mouseDragMode;
        private Point _mouseDragStart;
        private Point _mouseDragLast;
        private double _MouseDragStartTimeOffset;
        private double _MouseDragStartYOffset;
        private Point _MouseLastHoverPos;

        private IList<ProfilerTraceInstance> _traces;

        public GraphComponent()
        {
            _timelineStyle = null;
            _timelineParams = null;

            // set up event handlers
            MouseLeave += new MouseEventHandler( OnMouseLeave );
            MouseMove += new MouseEventHandler( OnMouseMove );
            SizeChanged += new SizeChangedEventHandler( OnSizeChanged );

            children = new VisualCollection( this );
            drawingVisual = new DrawingVisual();
            children.Add( drawingVisual );

            _mouseDragLast = new Point( 0, 0 );
            _MouseLastHoverPos = new Point( -1, -1 );
            _mouseDragMode = MouseDragMode.None;
            _MouseDragStartYOffset = 0;
        }

        public void SetUp( IList<ProfilerTraceInstance> traces, TimelineStyle timelineStyle, TimelineParams timelineParams )
        {
            _traces = traces;
            _timelineStyle = timelineStyle;
            _timelineParams = timelineParams;
        }

        #region DrawingVisual stuff

        private VisualCollection children;
        private DrawingVisual drawingVisual;

        protected override int VisualChildrenCount
        {
            get { return children.Count; }
        }

        protected override Visual GetVisualChild( int index )
        {
            if ( index < 0 || index >= children.Count )
                throw new ArgumentOutOfRangeException();

            return children[index];
        }

        #endregion DrawingVisual stuff

        #region Event handlers

        public void OnMouseMove( object sender, MouseEventArgs e )
        {
            Point p = e.GetPosition( this );

            if ( _MouseLastHoverPos.X != p.X || _MouseLastHoverPos.Y != p.Y )
            {
                _MouseLastHoverPos = p;
                Draw();
            }
        }

        public void OnMouseLeave( object sender, MouseEventArgs e )
        {
            _MouseLastHoverPos.X = -1;
            _MouseLastHoverPos.Y = -1;
        }

        public void OnFocusLost( object sender, RoutedEventArgs e )
        {
            if ( _mouseDragMode != MouseDragMode.None )
            {
                _mouseDragMode = MouseDragMode.None;
                ReleaseMouseCapture();
                Draw();
            }
        }

        public void OnMouseWheel( object sender, MouseWheelEventArgs e )
        {
            Point p = e.GetPosition( this );
            double timeAtPoint = _timelineParams.CanvasXToTime( p.X );
            _timelineParams.Zoom( e.Delta );
            _timelineParams.TimeOffset = timeAtPoint - (p.X * _timelineParams.SecondsPerPixel);

            if ( _mouseDragMode != MouseDragMode.None )
            {
                _mouseDragStart = p;
                _MouseDragStartTimeOffset = _timelineParams.TimeOffset;
                _MouseDragStartYOffset = _timelineParams.YOffset;
            }

            Draw();
        }

        public void OnSizeChanged( object sender, SizeChangedEventArgs e )
        {
            Draw();
        }

        #endregion Event handlers

        public void Draw()
        {
            if ( _timelineStyle == null || _timelineParams == null || _traces == null )
                return;

            // Retrieve the DrawingContext in order to create new drawing content.
            DrawingContext ctx = drawingVisual.RenderOpen();

            // main clip
            ctx.PushClip( new RectangleGeometry( new Rect( 0, 0, ActualWidth, ActualHeight ) ) );

            // clear
            {
                Rect rect = new Rect( new Point( 0, 0 ), new Size( ActualWidth, ActualHeight ) );
                ctx.DrawRectangle( _timelineStyle.BackgroundBrush, null, rect );
            }

            TimeBar.DrawBackground( ctx, ActualWidth, ActualHeight, _timelineParams, _timelineStyle );

            List<Graph> visibleGraphs = new List<Graph>();
            List<KeyValuePair<Graph, double>> graphValues = null;

            // find visible graphs
            foreach ( var trace in _traces )
            {
                foreach ( var dataView in trace.DataViews )
                {
                    if ( !dataView.Visible )
                        continue;

                    foreach ( var graph in dataView.Graphs )
                    {
                        if ( !graph.Visible )
                            continue;

                        visibleGraphs.Add( graph );
                    }
                }
            }

            // draw hover
            if ( _MouseLastHoverPos.X != 0 && _MouseLastHoverPos.Y != 0 )
            {
                graphValues = new List<KeyValuePair<Graph, double>>();

                double hoverTime = _timelineParams.CanvasXToTime( _MouseLastHoverPos.X );
                ctx.DrawLine( _timelineStyle.GroupLinePen,
                    new Point( _MouseLastHoverPos.X, 0.0 ),
                    new Point( _MouseLastHoverPos.X, ActualHeight ) );

                // find value at hovered time for each graph
                foreach ( var graph in visibleGraphs )
                {

                    double val = graph.GetValueFor( hoverTime );
                    graphValues.Add( new KeyValuePair<Graph, double>( graph, val ) );

                    // scale
                    double scaledVal = ActualHeight - ActualHeight * val / graph.Max;
                    ctx.DrawLine( _timelineStyle.GroupLinePen,
                        new Point( 0, scaledVal ),
                        new Point( ActualWidth, scaledVal ) );
                }
            }

            // draw
            foreach ( var graph in visibleGraphs )
                DrawGraph( ctx, graph );

            // write values
            if ( graphValues != null )
            {
                double y = 4;
                foreach ( var pair in graphValues )
                {
                    Graph graph = pair.Key;

                    string str = graph.Name + ": " + Logic.Utils.FormatValue( pair.Value, graph.Type );
                    FormattedText text = new FormattedText( str, _timelineStyle.CultureInfo,
                        FlowDirection.LeftToRight, _timelineStyle.TextTypeface, 11.0, graph.Brush );
                    ctx.DrawText( text, new Point( 4, y ) );

                    y += 20;
                }
            }

            ctx.Pop();
            ctx.Close();
        }

        void TraverseGraphTree( GraphNode node, List<KeyValuePair<double, double>> list )
        {
            double maxResolution = 2.5;
            double leftTime = _timelineParams.TimeToCanvasX( node.Start );
            double rightTime = _timelineParams.TimeToCanvasX( node.End );

            // leaf or merged points
            if ( node.Left == null || node.Right == null || (rightTime - leftTime < maxResolution) )
            {
                list.Add( new KeyValuePair<double, double>( leftTime, node.value ) );
                return;
            }

            double splitTime = _timelineParams.TimeToCanvasX( node.Split );

            // TODO this not work - missing lines
            // we always go to at least one node in order to include lines on the component boundary
            // (point outside)
            if ( splitTime < 0 )
                TraverseGraphTree( node.Right, list );
            else if ( splitTime > ActualWidth )
                TraverseGraphTree( node.Left, list );
            else
            {
                TraverseGraphTree( node.Left, list );
                TraverseGraphTree( node.Right, list );
            }
        }

        void DrawGraph( DrawingContext ctx, Graph graph )
        {
            if ( graph.Values.Count < 2 )
                return;

            double Yscale = ActualHeight / graph.Max;

            List<KeyValuePair<double, double>> list = new List<KeyValuePair<double, double>>();

            double leftTime = _timelineParams.CanvasXToTime( 0.0 );
            double rightTime = _timelineParams.CanvasXToTime( ActualWidth );

            list.Add( new KeyValuePair<double, double>( 0, graph.GetValueFor( leftTime ) ) );
            TraverseGraphTree( graph.RootNode, list );
            list.Add( new KeyValuePair<double, double>( ActualWidth, graph.GetValueFor( rightTime ) ) );

            bool first = true;
            Point prev = new Point();
            foreach ( var p in list )
            {
                Point point = new Point( p.Key, ActualHeight - p.Value * Yscale );

                if ( first )
                    first = false;
                else
                    ctx.DrawLine( graph.Pen, prev, point );

                prev = point;
            }

        }
    }
}
